using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;

namespace IUPUI.StellarResults
{
	/// <summary>
	/// Represents a timer to time <see cref="AstronomicalObject"/>s.
	/// </summary>
	[Serializable]
	public class AstronomicalTimer : ININ.Disposable, IComponent
	{
		#region Private Properties
		private System.Timers.Timer m_Timer;
		private DateTime m_CurrentDateTimeUTC;
		private DateTimeInterval m_Speed =
			new DateTimeInterval(5, ININ.DateTimeUnit.Minutes);

		private EventHandler m_TimerElapsed;
		private EventHandler<ININ.EventArgs<DateTime>> m_CurrentDateTimeChanged;
		private EventHandler<ININ.EventArgs<bool>> m_EnabledChanged;
		private EventHandler<ININ.EventArgs<DateTimeInterval>> m_SpeedChanged;
		#endregion Private Properties

		#region Public Events
		/// <summary>
		/// Occurs when [current date time changed].
		/// </summary>
		public event EventHandler<ININ.EventArgs<DateTime>> CurrentDateTimeChanged
		{
			add
			{
				m_CurrentDateTimeChanged += value;
			}
			remove
			{
				m_CurrentDateTimeChanged -= value;
			}
		}

		/// <summary>
		/// Occurs when [timer elapsed].
		/// </summary>
		public event EventHandler TimerElapsed
		{
			add
			{
				m_TimerElapsed += value;
			}
			remove
			{
				m_TimerElapsed -= value;
			}
		}

		/// <summary>
		/// Occurs when [enabled changed].
		/// </summary>
		public event EventHandler<ININ.EventArgs<bool>> EnabledChanged
		{
			add
			{
				m_EnabledChanged += value;
			}
			remove
			{
				m_EnabledChanged -= value;
			}
		}

		/// <summary>
		/// Occurs when [speed changed].
		/// </summary>
		public event EventHandler<ININ.EventArgs<DateTimeInterval>> SpeedChanged
		{
			add
			{
				m_SpeedChanged += value;
			}
			remove
			{
				m_SpeedChanged -= value;
			}
		}
		#endregion Public Events

		#region Public Properties
		/// <summary>
		/// Gets a value indicating whether this <see cref="AstronomicalTimer"/> is enabled.
		/// </summary>
		/// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
		public bool Enabled
		{
			get
			{
				return m_Timer.Enabled;
			}
		}

		/// <summary>
		/// Gets or sets the current date time (UTC).
		/// </summary>
		/// <value>The current date time (UTC).</value>
		public DateTime CurrentDateTimeUTC
		{
			get
			{
				return m_CurrentDateTimeUTC;
			}
			set
			{
				m_CurrentDateTimeUTC = value;
				OnCurrentDateTimeChanged();
			}
		}

		/// <summary>
		/// Gets or sets the speed.
		/// </summary>
		/// <value>The speed.</value>
		public DateTimeInterval Speed
		{
			get
			{
				return m_Speed;
			}
			set
			{
				if (m_Speed != value)
				{
					m_Speed = value;
					m_Speed.PropertyChanged +=
						new PropertyChangedEventHandler(m_Speed_PropertyChanged);
					OnSpeedChanged();
				}
			}
		}
		#endregion Public Properties

		#region Constructors
		/// <summary>
		/// Initializes a new instance of the <see cref="AstronomicalTimer"/> class.
		/// </summary>
		public AstronomicalTimer()
		{
			// Initialize time to now, if its not yet set
			if (m_CurrentDateTimeUTC == DateTime.MinValue)
			{
				m_CurrentDateTimeUTC = DateTime.Now;
			}


			m_Speed.PropertyChanged +=
				new PropertyChangedEventHandler(m_Speed_PropertyChanged);

			m_Timer = new System.Timers.Timer(1000);
			m_Timer.Elapsed += new System.Timers.ElapsedEventHandler(m_Timer_Elapsed);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="AstronomicalTimer"/> class.
		/// </summary>
		/// <param name="container">The container.</param>
		public AstronomicalTimer(
			IContainer container) : this()
		{
			container.Add(this);
		}
		#endregion Constructors

		#region Public Methods
		/// <summary>
		/// Starts this instance.
		/// </summary>
		public void Start()
		{
			if (!m_Timer.Enabled)
			{
				m_Timer.Start();
				OnEnabledChanged();
			}
		}

		/// <summary>
		/// Stops this instance.
		/// </summary>
		public void Stop()
		{
			if (m_Timer.Enabled)
			{
				m_Timer.Stop();
				OnEnabledChanged();
			}
		}
		#endregion Public Methods

		/// <summary>
		/// Handles the Elapsed event of the m_Timer control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Timers.ElapsedEventArgs"/> instance containing the event data.</param>
		private void m_Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
		{
			switch(m_Speed.Unit)
			{
				case ININ.DateTimeUnit.Milliseconds:
					CurrentDateTimeUTC = m_CurrentDateTimeUTC.AddMilliseconds(m_Speed.Value);
					break;
				case ININ.DateTimeUnit.Seconds:
					CurrentDateTimeUTC = m_CurrentDateTimeUTC.AddSeconds(m_Speed.Value);
					break;
				case ININ.DateTimeUnit.Minutes:
					CurrentDateTimeUTC = m_CurrentDateTimeUTC.AddMinutes(m_Speed.Value);
					break;
				case ININ.DateTimeUnit.Hours:
					CurrentDateTimeUTC = m_CurrentDateTimeUTC.AddHours(m_Speed.Value);
					break;
				case ININ.DateTimeUnit.Days:
					CurrentDateTimeUTC = m_CurrentDateTimeUTC.AddDays(m_Speed.Value);
					break;
				default:
					throw new ArgumentException(
						Properties.Resources.InvalidAstronomicalTimeFromDateTimeUnit);
			}

			// Fire event
			if (m_TimerElapsed != null)
			{
				m_TimerElapsed(this, EventArgs.Empty);
			}
		}

		/// <summary>
		/// Handles the PropertyChanged event of the m_Speed control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
		private void m_Speed_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			OnSpeedChanged();
		}

		/// <summary>
		/// Called when [current date time changed].
		/// </summary>
		protected virtual void OnCurrentDateTimeChanged()
		{
			// Fire event
			if (m_CurrentDateTimeChanged != null)
			{
				m_CurrentDateTimeChanged(this, new ININ.EventArgs<DateTime>(CurrentDateTimeUTC));
			}
		}

		/// <summary>
		/// Called when [enabled changed].
		/// </summary>
		protected virtual void OnEnabledChanged()
		{
			// Fire event
			if (m_EnabledChanged != null)
			{
				m_EnabledChanged(this, new ININ.EventArgs<bool>(m_Timer.Enabled));
			}
		}

		/// <summary>
		/// Called when [speed changed].
		/// </summary>
		protected virtual void OnSpeedChanged()
		{
			// Fire event
			if (m_SpeedChanged != null)
			{
				m_SpeedChanged(
					this, 
					new ININ.EventArgs<DateTimeInterval>(new DateTimeInterval(m_Speed.Value, m_Speed.Unit)));
			}
		}

		/// <summary>
		/// Releases unmanaged and - optionally - managed resources
		/// </summary>
		/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				// Clear events
				m_CurrentDateTimeChanged = null;
				m_TimerElapsed = null;
				m_EnabledChanged = null;
				m_SpeedChanged = null;

				// Dispose of timer
				if (m_Timer != null)
				{
					m_Timer.Dispose();
				}
			}

			base.Dispose(disposing);
		}

		#region IComponent Members
		private System.ComponentModel.ISite m_Site;

		/// <summary>
		/// Gets or sets the <see cref="T:System.ComponentModel.ISite"/> associated with the <see cref="T:System.ComponentModel.IComponent"/>.
		/// </summary>
		/// <value></value>
		/// <returns>The <see cref="T:System.ComponentModel.ISite"/> object associated with the component; or null, if the component does not have a site.</returns>
		public System.ComponentModel.ISite Site
		{
			get
			{
				return m_Site;
			}
			set
			{
				m_Site = value;
			}
		}
		#endregion IComponent Members
	}
}
